﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using FNN.Networks;
using FNN.SourcePoints;
using FNN.Genetic.Managers;
using FNN.Genetic;
using FNN.Genetic.Mutations;
using FNN.Intervals;
using FNN.NetworkComponents;

namespace FNN.NetworkMasters
{
    public class FinancialNetworkMaster : NetworkMaster
    {
        protected const double InitialFunds = 100000;
        protected List<SourcePoint> DataSourceValue;
        protected int ProfitPercentValue;
        protected int GeneticProfitPercentValue;

        /// <summary>
        ///     Глобальный желаемый процент прибыли при культивации сети
        /// </summary>
        public int GeneticProfitPercent
        {
            get { return GeneticProfitPercentValue; }
            set { GeneticProfitPercentValue = value; }
        }

        /// <summary>
        ///     Желаемый процент прибыли
        /// </summary>
        public int ProfitPercent
        {
            get { return ProfitPercentValue; }
            set { ProfitPercentValue = value; }
        }

        /// <summary>
        ///     Исходные данные
        /// </summary>
        public List<SourcePoint> DataSource
        {
            get { return DataSourceValue; }
            set { DataSourceValue = value; }
        }

        public event IterationChangedContainer IterationChanged;
        public event IterationChangedContainer GenerationChanged;
        public event IterationResultsImprovedContainer IterationResultsImproved;
        public event IterationResultsImprovedWithOperationCount ResultsImprovedWithCount;
        public event IterationResultsImprovedContainer GenerationResultsImproved;

        /// <summary>
        ///     Пройти эпоху обучения
        /// </summary>
        /// <param name="target">Целевая сеть</param>
        /// <param name="isMultiNetwork">Множественность выходов сети</param>
        public override void RunEpoch(ref Network target, bool isMultiNetwork)
        {
            ((FinancialNetwork)target).Transactions = new List<NetworkComponents.NetworkTransaction>();
            var coordinatesCount = DataSourceValue.First().Coordinates.Count;
            var inputsForCoordinate = target.InputCount / coordinatesCount;

            var usedTarget = (FinancialNetwork)target;
            usedTarget.Funds = InitialFunds;
            usedTarget.Stock = 0;

            for (var j = 0; j < (DataSourceValue.Count() - (inputsForCoordinate)); j++)
            {
                for (var k = 0; k < inputsForCoordinate; k++)
                {
                    for (var i = 0; i < coordinatesCount; i++)
                    {
                        target.Layers.ElementAt(0).Neurons.ElementAt((k * coordinatesCount) + i).InputValue =
                            DataSourceValue[j + k].Coordinates.Values.ElementAt(i);
                    }
                }

                var networkResult = usedTarget.GetNetworkResult();

                var rangeList = new List<ValueInterval>
                {
                    new ValueInterval {Start = 0, End = usedTarget.BuyBorder, Value = -1},
                    new ValueInterval {Start = usedTarget.BuyBorder, End = usedTarget.StandbyBorder, Value = 0},
                    new ValueInterval {Start = usedTarget.StandbyBorder, End = usedTarget.SellBorder, Value = 1}
                };

                int result;
                if (isMultiNetwork)
                {
                    var networkResults = target.GetNetworkResults();
                    result = networkResults.IndexOf(networkResults.Max(o => o)) - 1;
                    if (j == ((DataSourceValue.Count() - (inputsForCoordinate)) - 1))
                    {
                        result = 1;
                    }
                }
                else
                {
                    result = rangeList.First(o => (o.Start <= networkResult) && (o.End >= networkResult)).Value;
                    if (j == (DataSourceValue.Count() - (inputsForCoordinate) - 1))
                    {
                        result = 1;
                    }
                }

                switch (result)
                {
                    case -1:
                        usedTarget.BuyStock(DataSourceValue[j].Coordinates.First(o => o.Key.Contains("max")).Value,
                            ((DateSourcePoint)DataSourceValue[j]).PointDate);
                        break;
                    case 1:
                        usedTarget.SellStock(DataSourceValue[j].Coordinates.First(o => o.Key.Contains("min")).Value,
                            ((DateSourcePoint)DataSourceValue[j]).PointDate);
                        break;
                }
            }
        }

        /// <summary>
        ///     Обучить сеть
        /// </summary>
        /// <param name="target">Целевая сеть</param>
        /// <returns></returns>
        public override Network TrainNetwork(Network target)
        {
            var usedTarget = (FinancialNetwork)target;
            bool needToContinue;
            var timeElapsed = false;
            var iterationCount = 0;

            if (TimeLimitValue != 0)
            {
                var workTimer = new Timer { Interval = (TimeLimitValue * 1000 * 60 * 60) };
                workTimer.Elapsed += (sender, args) => timeElapsed = true;
                workTimer.Enabled = true;
            }

            double bestFunds = 0;

            var synapsesChangeCount =
                ((Convert.ToInt32(ConfigurationManager.AppSettings["useDefaultSynapsesChangeCount"]) == 1) &&
                 (usedTarget.Synapses.Count >=
                  Convert.ToInt32(ConfigurationManager.AppSettings["synapsesChangeCount"])))
                    ? Convert.ToInt32(ConfigurationManager.AppSettings["synapsesChangeCount"])
                    : usedTarget.LayerCount;

            do
            {
                var synapsesChangeDictionary = new Dictionary<int, double>();

                for (var i = 0; i < synapsesChangeCount; i++)
                {
                    var randomChangeIndex = Randomizer.Next(0, target.Synapses.Count);
                    while (synapsesChangeDictionary.ContainsKey(randomChangeIndex))
                    {
                        randomChangeIndex = Randomizer.Next(0, target.Synapses.Count);
                    }
                    synapsesChangeDictionary.Add(randomChangeIndex, 0);
                }

                target.RecalculateWeights(ref synapsesChangeDictionary);
                RunEpoch(ref target, false);
                iterationCount++;
                if (IterationChanged != null) IterationChanged(iterationCount);

                if (usedTarget.Funds > bestFunds)
                {
                    bestFunds = usedTarget.Funds;
                    if (IterationResultsImproved != null) IterationResultsImproved(iterationCount, bestFunds);
                    if (ResultsImprovedWithCount != null)
                        ResultsImprovedWithCount(bestFunds, usedTarget.Transactions.Count);
                }
                else
                {
                    usedTarget.CancelChange(synapsesChangeDictionary);
                }

                var iterationsCompleted = (IterationsLimitValue != 0) && (iterationCount >= IterationsLimitValue);
                var profitCompleted = (ProfitPercentValue != 0) &&
                                      (((bestFunds - InitialFunds) / InitialFunds * 100) >= ProfitPercentValue);
                needToContinue = !iterationsCompleted && !profitCompleted;
            } while (needToContinue && (!timeElapsed));

            return usedTarget;
        }

        protected List<NetworkTransaction> CopyTransactionList(List<NetworkTransaction> source)
        {
            var result = new List<NetworkTransaction>();

            foreach (var transaction in source)
            {
                var newTransaction = new NetworkTransaction(transaction);
                result.Add(newTransaction);
            }

            return result;
        }

        /// <summary>
        ///     Обучить сеть с множественными выходами
        /// </summary>
        /// <param name="target">Целевая сеть</param>
        /// <returns></returns>
        public override Network TrainMultiNetwork(Network target)
        {
            bool needToContinue;
            int iterationCount = 0;

            var usedTarget = (FinancialNetwork)target;
            double bestFunds = usedTarget.Funds;
            var timeElapsed = false;
            List<NetworkTransaction> bestNetworkTransactions = usedTarget.Transactions;

            if (TimeLimitValue != 0)
            {
                var workTimer = new Timer { Interval = (TimeLimitValue * 1000 * 60 * 60) };
                workTimer.Elapsed += (sender, args) => timeElapsed = true;
                workTimer.Enabled = true;
            }

            var synapsesChangeCount =
                ((Convert.ToInt32(ConfigurationManager.AppSettings["useDefaultSynapsesChangeCount"]) == 1) &&
                 (usedTarget.Synapses.Count >=
                  Convert.ToInt32(ConfigurationManager.AppSettings["synapsesChangeCount"])))
                    ? Convert.ToInt32(ConfigurationManager.AppSettings["synapsesChangeCount"])
                    : usedTarget.LayerCount;

            do
            {
                var synapsesChangeDictionary = new Dictionary<int, double>();

                for (var i = 0; i < synapsesChangeCount; i++)
                {
                    var randomChangeIndex = Randomizer.Next(0, target.Synapses.Count);
                    while (synapsesChangeDictionary.ContainsKey(randomChangeIndex))
                    {
                        randomChangeIndex = Randomizer.Next(0, target.Synapses.Count);
                    }
                    synapsesChangeDictionary.Add(randomChangeIndex, 0);
                }

                target.RecalculateWeights(ref synapsesChangeDictionary);
                RunEpoch(ref target, true);
                iterationCount++;
                if (IterationChanged != null) IterationChanged(iterationCount);

                if (usedTarget.Funds > bestFunds)
                {
                    bestFunds = usedTarget.Funds;
                    if (IterationResultsImproved != null) IterationResultsImproved(iterationCount, usedTarget.Funds);
                    if (ResultsImprovedWithCount != null)
                        ResultsImprovedWithCount(bestFunds, usedTarget.Transactions.Count);
                    bestNetworkTransactions = CopyTransactionList(usedTarget.Transactions);
                }
                else
                {
                    usedTarget.CancelChange(synapsesChangeDictionary);
                }

                var iterationsCompleted = (IterationsLimitValue != 0) && (iterationCount >= IterationsLimitValue);
                var profitCompleted = (ProfitPercentValue != 0) &&
                                      (((bestFunds - InitialFunds) / InitialFunds * 100) >= ProfitPercentValue);
                needToContinue = !iterationsCompleted && !profitCompleted;
            } while (needToContinue && (!timeElapsed));

            usedTarget.Transactions = bestNetworkTransactions;
            usedTarget.Funds = bestFunds;
            return usedTarget;
        }

        /// <summary>
        ///     Обучить сеть с помощью генетического алгоритма
        /// </summary>
        /// <param name="target">Целевая сеть</param>
        /// <param name="isMultiNetwork">Множественность выходов сети</param>
        /// <returns></returns>
        public override Network GeneticNetworkTraining(Network target, bool isMultiNetwork)
        {
            var usedTarget = (FinancialNetwork)target;
            bool needToContinue;
            var timeElapsed = false;
            var iterationCount = 0;
            double bestFunds = 0;

            if (TimeLimitValue != 0)
            {
                var workTimer = new Timer { Interval = (TimeLimitValue * 1000 * 60 * 60) };
                workTimer.Elapsed += (sender, args) => timeElapsed = true;
                workTimer.Enabled = true;
            }

            do
            {
                usedTarget = (FinancialNetwork)GeneticTrainingEpoch(usedTarget, isMultiNetwork);
                iterationCount++;
                if (IterationChanged != null) IterationChanged(iterationCount);

                if (usedTarget.Funds > bestFunds)
                {
                    bestFunds = usedTarget.Funds;
                    if (IterationResultsImproved != null) IterationResultsImproved(iterationCount, usedTarget.Funds);
                }

                var iterationsCompleted = (IterationsLimitValue != 0) && (iterationCount >= IterationsLimitValue);
                var profitCompleted = (ProfitPercentValue != 0) &&
                                      (((bestFunds - InitialFunds) / InitialFunds * 100) >= ProfitPercentValue);
                needToContinue = !iterationsCompleted && !profitCompleted;
            } while (needToContinue && (!timeElapsed));

            return usedTarget;
        }


        /// <summary>
        ///     Культивирование сети с использованием генетического алгоритма
        /// </summary>
        /// <param name="progenitor">Предок</param>
        /// <returns></returns>
        public override Network NetworkCultivation(Network progenitor, bool isMultiNetwork)
        {
            var usedProgenitor = (FinancialNetwork)progenitor;
            bool needToContinue;
            var timeElapsed = false;
            var iterationCount = 0;
            double bestFunds = 0;

            if (GeneticTimeLimitValue != 0)
            {
                var workTimer = new Timer() { Interval = (GeneticTimeLimitValue * 1000 * 60 * 60) };
                workTimer.Elapsed += (sender, args) => timeElapsed = true;
                workTimer.Enabled = true;
            }

            var lastGeneration = CreateGeneration(progenitor, PopulationCountValue);
            var currentGeneration = NetworkCultivationEpoch(lastGeneration, isMultiNetwork);
            lastGeneration = currentGeneration;
            iterationCount++;
            if (GenerationChanged != null) GenerationChanged(iterationCount);

            do
            {
                currentGeneration = NetworkCultivationEpoch(CreateGeneration(lastGeneration, 2), isMultiNetwork);
                lastGeneration = currentGeneration;
                iterationCount++;
                if (GenerationChanged != null) GenerationChanged(iterationCount);

                FinancialNetwork bestNetwork = (FinancialNetwork)currentGeneration.OrderByDescending(o => ((FinancialNetwork)o).Funds).FirstOrDefault();
                var currentFunds = bestNetwork.Funds;

                if (currentFunds > bestFunds)
                {
                    usedProgenitor = bestNetwork;
                    bestFunds = currentFunds;
                    if (GenerationResultsImproved != null) GenerationResultsImproved(iterationCount, currentFunds);
                }

                var iterationsCompleted = (GeneticIterationsLimitValue != 0) && (iterationCount >= GeneticIterationsLimitValue);
                var profitCompleted = (ProfitPercentValue != 0) &&
                                      (((bestFunds - InitialFunds) / InitialFunds * 100) >= ProfitPercentValue);
                needToContinue = !iterationsCompleted && !profitCompleted;

            } while (needToContinue && (!timeElapsed));

            return usedProgenitor;
        }

        /// <summary>
        ///     Пройти эпоху генетического обучения
        /// </summary>
        /// <param name="progenitor">Предок</param>
        /// <param name="isMultiNetwork">Множественность выходов сети</param>
        /// <returns>Лучший потомок</returns>
        public override Network GeneticTrainingEpoch(Network progenitor, bool isMultiNetwork)
        {
            var networksList = new List<FinancialNetwork>();
            var randomizer = new Random();
            Parallel.For(0, PopulationCountValue, i =>
            {
                Network newFinancialNetwork = new FinancialNetwork((FinancialNetwork)progenitor);
                var indexDictionary = new Dictionary<int, double>();
                for (var j = 0; j < newFinancialNetwork.Synapses.Count * 5 / 10; j++)
                {
                    bool keyExists;
                    int randomValueIndex;
                    do
                    {
                        randomValueIndex = randomizer.Next(0, newFinancialNetwork.Synapses.Count);
                        keyExists = indexDictionary.ContainsKey(randomValueIndex);
                    } while (keyExists);

                    indexDictionary.Add(randomValueIndex, 0);
                }
                newFinancialNetwork.RecalculateWeights(ref indexDictionary);
                RunEpoch(ref newFinancialNetwork, isMultiNetwork);
                networksList.Add((FinancialNetwork)newFinancialNetwork);
            });

            Network bestNetwork = networksList.FirstOrDefault(o => o.Funds == networksList.Max(p => p.Funds));
            return bestNetwork;
        }

        /// <summary>
        ///     Проход эпохи культивирования сети с нужной топологией
        /// </summary>
        /// <param name="targetGeneration">Текущее поколение</param>
        /// <param name="isMultiNetwork">Множественность выходов сети</param>
        /// <returns></returns>
        public override List<Network> NetworkCultivationEpoch(List<Network> targetGeneration, bool isMultiNetwork)
        {
            var bestNetworksList = new List<Network>();
            int bestNetworksCount = Convert.ToInt32(ConfigurationManager.AppSettings["bestNetworksNumber"]);

            Parallel.For(0, targetGeneration.Count(), i =>
            {
                if (isMultiNetwork)
                {
                    targetGeneration[i] = TrainMultiNetwork(targetGeneration.ElementAt(i));
                }
                else
                {
                    targetGeneration[i] = TrainNetwork(targetGeneration.ElementAt(i));
                }
            });

            var sortedNetworks = targetGeneration.OrderBy(o => ((FinancialNetwork)o).Funds);
            for (int i = 0; i < bestNetworksCount; i++)
            {
                bestNetworksList.Add(new FinancialNetwork((FinancialNetwork)sortedNetworks.ElementAt(i)));
            }

            FinancialCrossbreedingManager manager = new FinancialCrossbreedingManager();
            var result = manager.AdvancedSelection(bestNetworksList);

            return result;
        }

        /// <summary>
        ///     Генерация поколения на основе предка
        /// </summary>
        /// <param name="progenitor">Предок</param>
        /// <param name="neededPopulationCount">Необходимая численность</param>
        /// <returns>Новое поколение</returns>
        public override List<Network> CreateGeneration(Network progenitor, int neededPopulationCount)
        {
            var resultList = new List<Network>();
            int maxMutationCount = Convert.ToInt32(ConfigurationManager.AppSettings["maxMutationCount"]);
            var randomizer = new Random();

            for (int i = 0; i < neededPopulationCount; i++)
            {
                var networkChild = new FinancialNetwork((FinancialNetwork)progenitor);
                var mutationCount = randomizer.Next(0, maxMutationCount);

                var networkChildDna = new DnaHelix();
                networkChildDna.ParseNetwork(networkChild);

                for (int k = 0; k < mutationCount; k++)
                {
                    var mutationType = (MutationTypes)randomizer.Next(0, System.Enum.GetNames(typeof(MutationTypes)).Count());
                    var mutationTarget = (MutationTargets)randomizer.Next(0, System.Enum.GetNames(typeof(MutationTargets)).Count());
                    Mutation currentMutation = null;

                    switch (mutationTarget)
                    {
                        case MutationTargets.Layer:
                            currentMutation = new LayerMutation(mutationType) { TargetDna = networkChildDna };
                            break;
                        case MutationTargets.Synapse:
                            currentMutation = new SynapseMutation(mutationType) { TargetDna = networkChildDna };
                            break;
                    }

                    if (currentMutation != null)
                    {
                        var resultDna = currentMutation.TargetDna;
                        networkChild = new FinancialNetwork(resultDna.GetNetworkStructure());
                    }
                }

                resultList.Add(networkChild);
            }

            return resultList;
        }

        /// <summary>
        ///     Генерация поколения на основе предыдущего
        /// </summary>
        /// <param name="progenitorGeneration">Предыдущее поколение</param>
        /// <param name="populationMultiplier">Коэффициент увеличения популяции</param>
        /// <returns>Искомое поколение</returns>
        public override List<Network> CreateGeneration(List<Network> progenitorGeneration, int populationMultiplier)
        {
            var resultList = new List<Network>();
            int maxMutationCount = Convert.ToInt32(ConfigurationManager.AppSettings["maxMutationCount"]);
            var randomizer = new Random();

            for (int j = 0; j < progenitorGeneration.Count; j++)
            {
                for (int i = 0; i < populationMultiplier; i++)
                {
                    var networkChild = new FinancialNetwork((FinancialNetwork)progenitorGeneration[j]);
                    var mutationCount = randomizer.Next(0, maxMutationCount);

                    var networkChildDna = new DnaHelix();
                    networkChildDna.ParseNetwork(networkChild);

                    for (int k = 0; k < mutationCount; k++)
                    {
                        var mutationType = (MutationTypes)randomizer.Next(0, System.Enum.GetNames(typeof(MutationTypes)).Count());
                        var mutationTarget = (MutationTargets)randomizer.Next(0, System.Enum.GetNames(typeof(MutationTargets)).Count());
                        Mutation currentMutation = null;

                        switch (mutationTarget)
                        {
                            case MutationTargets.Layer:
                                currentMutation = new LayerMutation(mutationType) { TargetDna = networkChildDna };
                                break;
                            case MutationTargets.Synapse:
                                currentMutation = new SynapseMutation(mutationType) { TargetDna = networkChildDna };
                                break;
                        }

                        if (currentMutation != null)
                        {
                            var resultDna = currentMutation.TargetDna;
                            networkChild = new FinancialNetwork(resultDna.GetNetworkStructure());
                        }
                    }

                    resultList.Add(networkChild);
                }
            }

            return resultList;
        }
    }
}